﻿/****************************************************************************************************
	Copyright (C) 2010 RapidWebDev Organization (http://rapidwebdev.org)
	Author: Eunge, Legal Name: Jian Liu, Email: eunge.liu@RapidWebDev.org

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ****************************************************************************************************/

using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Xml;
using System.Xml.Schema;
using BaoJianSoft.Common;

namespace BaoJianSoft.MvcRouteBuilder
{
	/// <summary>
	/// Configuration parser for workshop controls.
	/// </summary>
	public static class ConfigurationInitializer
	{
		/// <summary>
		/// Get xml namespace manager. 
		/// No cache added here because in most cases configuration is loaded once in an application lifecycle.
		/// </summary>
		private static XmlNamespaceManager @XmlNamespaceManager
		{
			get
			{
				XmlNamespaceManager xmlNamespaceManager = new XmlNamespaceManager(new NameTable());
				xmlNamespaceManager.AddNamespace("p", "http://www.5vsoft.com/schemas/routebuilder");
				return xmlNamespaceManager;
			}
		}

		/// <summary>
		/// Get the xml schema instance to validate xml file.
		/// No cache added here because in most cases configuration is loaded once in an application lifecycle.
		/// </summary>
		private static XmlSchema @Schema
		{
			get
			{
				string schemaXml = Kit.GetManifestFile(typeof(ConfigurationInitializer), "routebuilder.xsd");
				using (StringReader sr = new StringReader(schemaXml))
				{
					return XmlSchema.Read(sr, null);
				}
			}
		}

		/// <summary>
		/// Read configured route elements into RouteTable automatically.
		/// </summary>
		/// <returns></returns>
		public static void Initialize()
		{
			RouteTable.Routes.Clear();

			// Grab the Routes from Web.config
			RouteConfiguration routeConfig = System.Configuration.ConfigurationManager.GetSection("routeTable") as RouteConfiguration;
			if (routeConfig == null)
			{
				throw new ConfigurationErrorsException("The configuration section RouteTable doesn't exist.");
			}

			string filePath = routeConfig.FilePath;
			if (filePath.StartsWith("~/"))
			{
				filePath = HttpContext.Current.Server.MapPath(filePath);
			}

			XmlDocument xmldoc = ValidateInputFile(filePath);

			XmlNodeList ignoreNodes = xmldoc.SelectNodes("p:RouteTable/p:Ignores/p:Ignore", @XmlNamespaceManager);
			foreach (XmlNode ignoreNode in ignoreNodes)
			{
				string url = ReadElementAsString(ignoreNode, "p:Url");
				XmlNode constraintNode = ignoreNode.SelectSingleNode("p:Constraints", @XmlNamespaceManager);
				RouteTable.Routes.IgnoreRoute(url, ConvertElement2RouteValueDictionary(constraintNode));
			}

			XmlNodeList routeNodes = xmldoc.SelectNodes("p:RouteTable/p:Route", @XmlNamespaceManager);
			foreach (XmlNode routeNode in routeNodes)
			{
				string name = routeNode.Attributes["Name"].Value; 
				string routeUrl = ReadElementAsString(routeNode, "p:Url");
				string routeHandlerTypeName = ReadElementAsString(routeNode, "p:RouteHandler");
				IRouteHandler routerHandler = null;
				if (!Kit.IsEmpty(routeHandlerTypeName))
				{
					Type routeHandlerType = Kit.GetType(routeHandlerTypeName);
					if (routeHandlerType.GetInterface(typeof(IRouteHandler).FullName) == null)
					{
						throw new ConfigurationErrorsException(string.Format("Configured type {0} doesn't implement IRouteHandler.", routeHandlerTypeName));
					}

					ConstructorInfo constructor = routeHandlerType.GetConstructor(Type.EmptyTypes);
					if (constructor == null)
					{
						throw new ConfigurationErrorsException(string.Format("Configured type {0} doesn't have empty constructor.", routeHandlerTypeName));
					}

					routerHandler = constructor.Invoke(null) as IRouteHandler;
				}
				else
				{
					routerHandler = new MvcRouteHandler();
				}

				RouteValueDictionary defaults = null;
				XmlNode defaultNode = routeNode.SelectSingleNode("p:Defaults", @XmlNamespaceManager);
				if (defaultNode != null)
				{
					defaults = ConvertElement2RouteValueDictionary(defaultNode);
				}

				RouteValueDictionary constraints = null;
				XmlNode constraintNode = routeNode.SelectSingleNode("p:Constraints", @XmlNamespaceManager);
				if (constraintNode != null)
				{
					constraints = ConvertElement2RouteValueDictionary(constraintNode);
				}

				RouteTable.Routes.Add(name, new Route(routeUrl, defaults, constraints, routerHandler));
			}
		}


		/// <summary>
		/// Validate configured xml file.
		/// </summary>
		/// <param name="xmlFile"></param>
		/// <returns></returns>
		private static XmlDocument ValidateInputFile(string xmlFile)
		{
			if (!File.Exists(xmlFile))
			{
				throw new FileNotFoundException("File not found.", xmlFile);
			}

			try
			{
				XmlDocument xmldoc = new XmlDocument();
				xmldoc.Load(xmlFile);
				Kit.ValidateXml(@Schema, xmldoc);
				return xmldoc;
			}
			catch (XmlSchemaException)
			{
				throw;
			}
			catch (Exception exp)
			{
				throw new XmlSchemaException(string.Format("Specified xml file [{0}] is invalid.", xmlFile), exp);
			}
		}
		
		/// <summary>
		/// Read element value by xpath.
		/// </summary>
		/// <param name="parentNode"></param>
		/// <param name="xpath"></param>
		/// <returns></returns>
		private static string ReadElementAsString(XmlNode parentNode, string xpath)
		{
			XmlNode xmlnode = parentNode.SelectSingleNode(xpath, @XmlNamespaceManager);
			if (xmlnode == null) return null;
			return xmlnode.InnerText;
		}

		/// <summary>
		/// Convert input xml node to RouteValueDictionary instance.
		/// </summary>
		/// <param name="parentNode"></param>
		/// <returns></returns>
		private static RouteValueDictionary ConvertElement2RouteValueDictionary(XmlNode parentNode)
		{
			if (parentNode == null) return null;

			RouteValueDictionary results = new RouteValueDictionary();
			XmlNodeList itemNodes = parentNode.SelectNodes("p:Item", @XmlNamespaceManager);
			foreach (XmlNode itemNode in itemNodes)
			{
				string name = itemNode.Attributes["Name"].Value;
				string value = itemNode.Attributes["Value"].Value;
				if (itemNode.Attributes["Type"] == null)
				{
					results.Add(name, value);
				}
				else
				{
					string type = itemNode.Attributes["Type"].Value;
					Type parameterType = Kit.GetType(type);
					results.Add(name, Convert.ChangeType(value, parameterType));
				}
			}

			return results;
		}
	}
}

